package cn.zmlio.mybatisdbchecker.builder;

import cn.zmlio.mybatisdbchecker.genernator.MockDataGenerator;
import cn.zmlio.mybatisdbchecker.scanner.WarMapperScanner;
import cn.zmlio.mybatisdbchecker.utils.ZipUtils;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.*;

@Getter
@Slf4j
public class EnvironmentBuilder {

    private File warTmpDir;


    @SneakyThrows
    public void buildForWar(String warPath) {

        warTmpDir = ZipUtils.unzip(warPath, Files.createTempDirectory("war").toAbsolutePath().toString());
        WarMapperScanner scanner = new WarMapperScanner(warTmpDir.getAbsolutePath());
        scanner.scan();
        loadResources(scanner);
        Configuration configuration = parseMappers(scanner);
        Set<String> statementIds = scanner.getStatementIdsInXml();
        for (String statementId : statementIds) {
            MappedStatement mappedStatement = configuration.getMappedStatement(statementId);
            log.info("mappedStatement:{}", mappedStatement.getId());
            String type = scanner.getParameterTypeMap().get(statementId);
            try {
                BoundSql boundSql;
                if (type != null) {
                    Object param = null;
                    try {
                        param = MockDataGenerator.generateMockData(Class.forName(type));
                    } catch (ClassNotFoundException e) {
                        log.error("class not found:{}", type);
                    }
                    boundSql = mappedStatement.getBoundSql(param);
                } else {
                    Map<String, String> map = new HashMap<>();
                    map.put("id", "1");
                    map.put("tenant_id", "1");

                    boundSql = mappedStatement.getBoundSql(map);
                }
                String sql = generateSql(boundSql, configuration);
                log.info("Final SQL: {}", sql);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }

        }

    }

    private static String generateSql(BoundSql boundSql, Configuration configuration) {
        String sql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterObject != null && parameterMappings != null && !parameterMappings.isEmpty()) {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
                String propertyName = parameterMapping.getProperty();
                Object value = null;
                if (metaObject.hasGetter(propertyName)) {
                    value = metaObject.getValue(propertyName);
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                }
                if (value != null) {
                    String valueStr;
                    if (value instanceof String || value instanceof Date) {
                        valueStr = "'" + value + "'";
                    } else {
                        valueStr = value.toString();
                    }
                    sql = sql.replaceFirst("\\?", valueStr);
                }
            }
        }
        return sql;
    }

    private Configuration parseMappers(WarMapperScanner scanner) {
        Configuration configuration = new Configuration();
        for (String mapper : scanner.getMappers()) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(this.getClass().getClassLoader().getResourceAsStream(mapper), configuration, mapper, configuration.getSqlFragments());
            mapperParser.parse();
        }
        return configuration;
    }

    private void loadResources(WarMapperScanner scanner) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
        List<String> resourceFiles = new LinkedList<>();
        resourceFiles.addAll(scanner.getJarFiles());
        resourceFiles.addAll(scanner.getClassFiles());
        URL[] urls = resourceFiles.stream().map(p -> {
            try {
                return new URL("file://" + p);
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }).toArray(URL[]::new);
        URLClassLoader urlClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        addURL.setAccessible(true);
        for (URL url : urls) {
            addURL.invoke(urlClassLoader, url);
        }
        for (String type : scanner.getTypes()) {

            log.info("loading type:{}", type);
            if (type.split("\\.").length > 1) {
                Class<?> loadedClass = this.getClass().getClassLoader().loadClass(type);
                log.info("loaded class:{}", loadedClass);
            }
        }
    }

}

